package com.github.serverlicense;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import lombok.Setter;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author langle__
 * @version 1.2
 */
public class PluginModifier {

    private final Plugin plugin;
    private final Logger logger;

    @Setter
    private String pluginName;

    public PluginModifier(Plugin plugin) {
        this.plugin = plugin;
        this.logger = plugin.getLogger();
    }

    public void modifyJarWithJavassist(File inputFile, File outputFile) throws Exception {
        try (
                JarFile jarFile = new JarFile(inputFile);
                JarOutputStream output = new JarOutputStream(Files.newOutputStream(outputFile.toPath()))
        ) {
            boolean flag = false;
            if (isAlreadyModified(jarFile)) return;
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String entryName = entry.getName();

                output.putNextEntry(new JarEntry(entryName));
                try (InputStream input = jarFile.getInputStream(entry)) {
                    if (entryName.endsWith(".class")) {
                        modifyClassEntry(input, output);
                    } else if (entryName.equals("plugin.yml")) {
                        modifyPluginYml(input, output);
                    } else if (entryName.equals("META-INF/MANIFEST.MF")) {
                        modifyManifestFile(input, output);
                        flag = true;
                    } else {
                        copyEntryData(input, output);
                    }
                } catch (Exception e) {
                    logger.severe("Failed to modify jar: " + entryName);
                    logger.severe(e.getMessage());
                }

                output.closeEntry();
            }
            if (!flag) {
                output.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
                modifyManifestFile(new ByteArrayInputStream(new byte[0]), output);
            }
        }
    }

    private void modifyClassEntry(InputStream input, OutputStream output) throws Exception {
        byte[] classBytes = readAllBytes(input);

        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(new ByteArrayInputStream(classBytes));

        pool.importPackage("org.bukkit.Bukkit");
        pool.importPackage("org.bukkit.plugin.PluginManager");

        CtMethod method = null;
        try {
            method = ctClass.getDeclaredMethod("onEnable");
        } catch (NotFoundException ignored) {}
        if (method == null || method.isEmpty()) {
            output.write(classBytes);
            return;
        }

        method.insertBefore("if (Bukkit.getPluginManager().getPlugin(\"" + pluginName + "\") != null) Bukkit.getPluginManager().disablePlugin(this);");
        output.write(ctClass.toBytecode());
    }

    private void modifyPluginYml(InputStream input, OutputStream output) throws Exception {
        byte[] ymlBytes = readAllBytes(input);

        String ymlContent = new String(ymlBytes, StandardCharsets.UTF_8);
        YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(ymlContent));

        List<String> depend = config.getStringList("depend");
        if (!depend.contains(pluginName)) {
            depend.add(pluginName);
            config.set("depend", depend);
        }

        output.write(config.saveToString().getBytes(StandardCharsets.UTF_8));
    }

    public void modifyManifestFile(InputStream input, OutputStream output) throws Exception {
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));

        boolean flag = false;
        String line;
        while ((line = reader.readLine()) != null) {
            if (line.startsWith("Created-By:")) {
                writer.write("Created-By: ServerLicense");
                flag = true;
            } else {
                writer.write(line);
            }
            writer.newLine();
        }
        if (!flag) {
            writer.write("Created-By: ServerLicense");
            writer.newLine();
        }
        writer.flush();
    }

    public boolean isAlreadyModified(ZipFile zipFile) {
        ZipEntry entry = zipFile.getEntry("META-INF/MANIFEST.MF");
        if (entry == null) return false;
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(zipFile.getInputStream(entry)))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.equals("Created-By: ServerLicense")) return true;
            }
        } catch (IOException ignored) {}
        return false;
    }

    private byte[] readAllBytes(InputStream input) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int read;
        byte[] data = new byte[8192];
        while ((read = input.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, read);
        }
        return buffer.toByteArray();
    }

    private void copyEntryData(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[8192];
        int read;
        while ((read = input.read(buffer)) != -1) {
            output.write(buffer, 0, read);
        }
    }

}
